﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.Collections.Generic;
using Odyssey.Utils;
using System.Linq;

namespace Odyssey.Effects
{
    /// <summary>
    /// Page transition effect that performs a feathered turnstile effect.
    /// In order to enable this effect, all UIElements in the page template which apply to the effect must be decorated with FeatherEffect.IsElement.
    /// </summary>
    public class FeatherEffect : PageEffect
    {
        private double amplitude = 3.2;
        private bool fadeElements = !true;

        public double Angle { get; set; }

        /// <summary>
        /// Gets or sets the amplitude of the feather effect. 
        /// </summary>
        public double Amplitude { get { return amplitude; } set { amplitude = value; } }

        /// <summary>
        /// Gets or sets whether to fade elements.
        /// </summary>
        public bool FadeElements { get { return fadeElements; } set { fadeElements = value; } }


        /// <summary>
        /// Gets the starting angle of all descendant FrameworkElements of the container element which are marked with Turnstile.IsElement=true.
        /// </summary>
        /// <param name="obj">container element.</param>
        public static bool GetIsLast(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsLastProperty);
        }

        /// <summary>
        /// Sets the starting angle of all descendant FrameworkElements of the container element which are marked with Turnstile.IsElement=true, and changes the current opacity of each
        /// element to 0.0.
        /// </summary>
        /// <param name="obj">container element.</param>
        public static void SetIsLast(DependencyObject obj, bool value)
        {
            obj.SetValue(IsLastProperty, value);
        }

        /// <summary>
        /// Gets whether the element is marked to be the last element in the turnstile chain.
        /// </summary>
        public static readonly DependencyProperty IsLastProperty =
            DependencyProperty.RegisterAttached("IsLast", typeof(bool), typeof(FeatherEffect), new PropertyMetadata(false));


        /// <summary>
        /// Gets whether the element is an element of the turnstile chain.
        /// </summary>
        public static bool GetIsElement(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsElementProperty);
        }

        /// <summary>
        /// Sets whether the element is an element of the turnstile chain.
        /// </summary>
        public static void SetIsElement(DependencyObject obj, bool value)
        {
            obj.SetValue(IsElementProperty, value);
        }

        public static readonly DependencyProperty IsElementProperty =
            DependencyProperty.RegisterAttached("IsElement", typeof(bool), typeof(FeatherEffect), new PropertyMetadata(false));


        protected override void Activate(Microsoft.Phone.Controls.PhoneApplicationPage page, Storyboard storyboard)
        {
            Apply(storyboard, page, EasingMode.EaseIn);
        }

        protected override void Deactivate(Microsoft.Phone.Controls.PhoneApplicationPage page, Storyboard storyboard, bool isActive)
        {
            Apply(storyboard, page, EasingMode.EaseOut);
        }


        protected override void Initialize()
        {
            Duration = TimeSpan.FromSeconds(0.1);
            this.Angle = -112.0;
        }

        //public override void Stop()
        //{
        //    if (Storyboard != null && Storyboard.GetCurrentState() == ClockState.Active)
        //    {
        //        Storyboard.SkipToFill();
        //        Storyboard.Stop();
        //    }
        //    //base.Stop();
        //}


        private void Apply(Storyboard sb, PhoneApplicationPage page, EasingMode mode)
        {
            page.StopScrollPanels();
            bool isForward = mode == EasingMode.EaseIn;

            var effectiveElements = GetEffectiveItems(page);

            //if (isForward) effectiveElements.Reverse();

            double duration = this.Duration.TotalSeconds;

            double targetOpacity = isForward ? 0.0 : 1.0;
            double targetAngle = isForward ? Angle : 0.0;
            double startAngle = isForward ? 0.0 : Angle;
            double startOpacity = isForward ? 1.0 : 0.0;


            var isLast = effectiveElements.FirstOrDefault(c => GetIsLast(c));
            if (isLast != null)
            {
                effectiveElements.Remove(isLast);
                effectiveElements.Add(isLast);
            }


            int n = effectiveElements.Count;
            if (n > 0)
            {
                double divisor = n;

                double elementDuration = duration;
                double delay = elementDuration / divisor * Amplitude;
                double startDelay = 0.0;
                EasingFunctionBase angleEase = !isForward ? (EasingFunctionBase)new CircleEase { EasingMode = EasingMode.EaseOut }
                    : (EasingFunctionBase)new QuadraticEase { EasingMode = EasingMode.EaseIn };

                EasingFunctionBase opacityEase = new CubicEase { EasingMode = mode };
                if (!IsInitialized) page.Opacity = 0.0;

                foreach (FrameworkElement e in effectiveElements)
                {
                    e.AdjustPerspective(page, true);
                    PlaneProjection projection = e.Projection as PlaneProjection;

                    projection.RotationY = startAngle;
                    if (fadeElements) e.Opacity = startOpacity;
                    DoubleAnimation timeline = new DoubleAnimation
                    {
                        Duration = TimeSpan.FromSeconds(elementDuration),
                        BeginTime = TimeSpan.FromSeconds(startDelay),
                        To = targetAngle,
                        FillBehavior = FillBehavior.HoldEnd,
                        EasingFunction = angleEase
                        //EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut }

                    };
                    Storyboard.SetTarget(timeline, projection);
                    Storyboard.SetTargetProperty(timeline, new PropertyPath("RotationY"));
                    sb.Children.Add(timeline);

                    if (fadeElements)
                    {
                        timeline = new DoubleAnimation
                        {
                            BeginTime = TimeSpan.FromSeconds(startDelay),
                            Duration = TimeSpan.FromSeconds(elementDuration),
                            To = targetOpacity,
                            FillBehavior = FillBehavior.HoldEnd,
                            EasingFunction = opacityEase
                        };
                        Storyboard.SetTarget(timeline, e);
                        Storyboard.SetTargetProperty(timeline, new PropertyPath("Opacity"));
                        sb.Children.Add(timeline);
                    }

                    elementDuration += delay / 3;
                    startDelay += delay;
                }

                {
                    double d = GetDuratonInSeconds(sb);
                    DoubleAnimation timeline = new DoubleAnimation
                    {
                        Duration = TimeSpan.FromSeconds(d),
                        To = targetOpacity,
                        EasingFunction = opacityEase
                    };
                    Storyboard.SetTarget(timeline, page);
                    Storyboard.SetTargetProperty(timeline, new PropertyPath("Opacity"));
                    sb.Children.Add(timeline);
                }
            }
        }



        private static List<DependencyObject> GetEffectiveItems(FrameworkElement container)
        {
            var effectiveElements = container
                .GetOrderedVisualDescendants()
                //                .GetVisualDescendants()
                .Where(e => GetIsElement(e) && IsActiveElement(e, container))
                .ToList();

            return effectiveElements;
        }

        private static bool IsActiveElement(DependencyObject o, FrameworkElement container)
        {
            FrameworkElement e = o as FrameworkElement;

            bool isExcluded = e.GetVisualAncestors()
                .Cast<FrameworkElement>()
                .Any(c => c.IsHitTestVisible == false);

            if (!isExcluded) isExcluded = !IsVisibleInContainer(o as FrameworkElement, container);
            if (isExcluded)
            {
                e.Opacity = 1.0;
                PlaneProjection projection = e.GetPlaneProjection(false);
                if (projection != null) projection.RotationY = 0.0;
            }
            return !isExcluded;

        }

        private static bool IsVisibleInContainer(FrameworkElement c, FrameworkElement container)
        {
            try
            {
                var gt = c.TransformToVisual(container);
                if (gt == null) return false;

                Point pt;
                if (!gt.TryTransform(new Point(), out pt)) return false;

                return !(((pt.Y + c.ActualHeight) < 0.0) || (pt.Y >= container.ActualHeight) || (pt.X >= container.ActualWidth) || (pt.X + c.ActualWidth < 0.0));

            }
            catch
            {
                return false;
            }
        }
    }
}
